
import { Asset, JsonAsset, path, resources, sys } from "cc";
export default class pbkiller {

    root = 'protos';
    pb = null;
    files = [];

    private message_name_id_request = {};
    private message_id_name_request = {};
    private message_name_id_response = {};
    private message_id_name_response = {};
    private message_name_id_push = {};
    private message_id_name_push = {};

    init(ProtosMessageID: { [key: string]: string }, ProtosMessageName: { [key: string]: string }) {
        for (let messageName in ProtosMessageID) {
            if (messageName.split("_")[0].endsWith("Request")) {
                this.message_name_id_request[messageName] = ProtosMessageID[messageName];
            } else if (messageName.split("_")[0].endsWith("Response")) {
                this.message_name_id_response[messageName] = ProtosMessageID[messageName];
            } else if (messageName.split("_")[0].endsWith("Push")) {
                this.message_name_id_push[messageName] = ProtosMessageID[messageName];
            } else {
                console.log("================= pbkiller init invalid messageName", messageName);
            }
        }
        for (let messageId in ProtosMessageName) {
            if (ProtosMessageName[messageId].split("_")[0].endsWith("Request")) {
                this.message_id_name_request[messageId] = ProtosMessageName[messageId];
            } else if (ProtosMessageName[messageId].split("_")[0].endsWith("Response")) {
                this.message_id_name_response[messageId] = ProtosMessageName[messageId];
            } else if (ProtosMessageName[messageId].split("_")[0].endsWith("Push")) {
                this.message_id_name_push[messageId] = ProtosMessageName[messageId];
            } else {
                console.log("================= pbkiller init invalid messageId", messageId);
            }
        }

        // ProtoBuf.Util.IS_NODE = sys.isNative;
        // let cccVer = parseFloat(cc.ENGINE_VERSION.match(/\d+\.\d+/, '')[0]);
        // let cccVer = "3.0.0";
        // ProtoBuf.Util.IS_NODE = false;
        // ProtoBuf.Util.cccVer = cccVer;

        /**
         * 加载proto
         */
        let loadProto = ProtoBuf.loadProto.bind(ProtoBuf);
        ProtoBuf.loadProto = function (asset, builder, filename) {
            try {
                if (typeof asset === 'string') {
                    loadProto(asset, builder, filename);
                } else if (asset.text) {
                    loadProto(asset.text, builder, filename);
                }
            } catch (e) {
                console.warn(`${filename}: protobuf syntax error`);
            }
        };


        /**
         * 加载json文件 
         */
        ProtoBuf.loadJsonFile = function (filename, callback, builder) {
            if (callback && typeof callback === 'object')
                builder = callback,
                    callback = null;
            else if (!callback || typeof callback !== 'function')
                callback = null;
            if (callback)
                return ProtoBuf.Util.fetch(typeof filename === 'string' ? filename : filename["root"] + "/" + filename["file"], function (contents) {
                    if (contents === null) {
                        callback(Error("Failed to fetch file"));
                        return;
                    }
                    try {
                        callback(null, ProtoBuf.loadJson(JSON.parse(contents), builder, filename));
                    } catch (e) {
                        callback(e);
                    }
                });

            let url = typeof filename === 'object' ? filename["root"] + "/" + filename["file"] : filename;
            let json;
            if (ProtoBuf.preloaded) {
                let content = resources.get(url, JsonAsset);
                if (content) {
                    json = JsonAsset ? content.json : content;
                }

            } else {
                let content = ProtoBuf.Util.fetch(url);
                if (content) {
                    json = JSON.parse(content);
                }
            }

            return json ? ProtoBuf.loadJson(json, builder, filename) : null;
        };
    }

    public releaseData() {
        //TODO 释放数据资源
        // decRef 减计数
    }

    preload(callback: Function) {
        ProtoBuf.Util.fetch = resources.get.bind(resources);
        resources.loadDir(this.root, Asset, (finished: number, total: number) => {
            if (callback) callback(finished / total, "proto", false);
        }, (err, assets) => {
            ProtoBuf.preloaded = !err;
            let files = [];
            for (let i = 0; i < assets.length; ++i) {
                let textAsset: any = assets[i];
                textAsset.addRef();
                if (textAsset.text) {
                    files[i] = textAsset.name;
                }
            }
            this.files = files;
            this.loadAll();
            if (callback) callback(1, "proto", true);
            // this.test();
        });
    }

    /**
     * 加载文件proto文件，支持json、proto格式
     * @param {String|Array} files 
     */
    loadFromFile(fileNames, packageName) {
        if ((sys.isNative || sys.platform === sys.Platform.WECHAT_GAME) && !ProtoBuf.preloaded) {
            console.error('原生或微信小游戏上，需要先调用preload函数');
            return;
        }
        if (typeof fileNames === 'string') {
            fileNames = [fileNames];
        }

        let builder = ProtoBuf.newBuilder();
        builder.importRoot = this.root;
        // if (cccVer >= 2 || (cccVer >= 1.1 && cccVer < 1.2) && ProtoBuf.preloaded) {
        //     builder.importRoot = this.root;
        // } else {
        //     builder.importRoot = url.raw(`resources/${this.root}`);
        // }

        fileNames.forEach((fileName) => {
            let extname = path.extname(fileName);
            let fullPath = `${builder.importRoot}/${fileName}`;
            fullPath = path.mainFileName(fullPath);

            if (extname === '.proto') {
                ProtoBuf.loadProtoFile(fullPath, null, builder);
            } else if (extname === '.json') {
                ProtoBuf.loadJsonFile(fullPath, null, builder);
            } else {
                console.log(`nonsupport file extname, only support 'proto' or 'json'`);
            }
        });

        return builder.build(packageName);
    }

    /**
     * 加载所有proto文件
     * @param {String} extname 
     * @param {String} packageName 
     */
    loadAll(extname = 'proto', packageName = '') {
        if (this.root.endsWith('/') || this.root.endsWith('\\')) {
            this.root = this.root.substr(0, this.root.length - 1);
        }

        //获取this.root下的所有文件名
        let files = this.files.concat();
        files = files.map((filePath) => {
            return `${filePath}.${extname}`;
        });
        this.pb = this.loadFromFile(files, packageName);
    }

    // loadData(url, callback) {
    //     if (sys.isNative) {
    //         let data = jsb.fileUtils.getDataFromFile(url);
    //         setTimeout(() => {
    //             callback(data);    
    //         }, 0);
    //     } else {
    //         var xhr = ProtoBuf.Util.XHR();
    //         xhr.open('GET', url, true);
    //         xhr.setRequestHeader('Accept', 'text/plain');
    //         xhr.responseType = 'arraybuffer'; 
    //         if (typeof xhr.overrideMimeType === 'function') xhr.overrideMimeType('text/plain');
    //         if (callback) {
    //             xhr.onreadystatechange = function() {
    //                 if (xhr.readyState != 4) return;
    //                 if (/* remote */ xhr.status == 200 || /* local */ (xhr.status == 0))
    //                     callback(xhr.response);
    //                 else
    //                     callback(null);
    //             };
    //             if (xhr.readyState == 4)
    //                 return;
    //             xhr.send(null);
    //         }
    //     }
    // }

    decode(responseData) {
        let receiveData = {
            code: 0,
            session: 0,
            retName: null,
            retData: null,
            reqName: null,
            push: false
        };

        let dataView = new DataView(responseData);
        //1 pbBuffer长度（四个字节）
        let pbBufferLen = dataView.getInt32(0);
        //2 session长度（四个字节）
        let session = dataView.getInt32(4);
        //3 messageId（四个字节）
        let messageId = dataView.getInt32(8);
        //4 code（四个字节）
        let code = dataView.getInt32(12);
        //5 protobuf数据
        let protoArray = responseData.slice(16, 16 + pbBufferLen);

        let retName = this.message_id_name_response[messageId];
        if (!retName) {
            retName = this.message_id_name_push[messageId];
            if (retName) {
                receiveData.push = true;
            }
        }
        if (!retName) {
            console.error("================= pbkiller decode error! protoName not found! messageId = " + messageId);
        }
        let proto = this.pb.Protos[retName];
        if (!proto) {
            console.error("================= pbkiller encode error! proto not found! retName = " + retName);
        }
        let retData = proto.decode(protoArray);

        receiveData.code = code;
        receiveData.session = session;
        receiveData.retName = retName;
        receiveData.retData = retData;

        if (receiveData.push) {
            return receiveData;
        }

        let reqMessageId = messageId - 1;//与后端约定，减一得到reqMessageId
        receiveData.reqName = this.message_id_name_request[reqMessageId];
        if (!receiveData.reqName) {
            console.error("================= pbkiller decode error! reqName not found! reqMessageId = " + reqMessageId);
        }
        return receiveData;
    }

    encode(postData) {
        let session = postData.session;
        let reqName = postData.reqName;
        let reqData = postData.reqData;
        let proto = this.pb.Protos[reqName];
        if (!proto) {
            console.error("================= pbkiller encode error! proto not found! reqName = " + reqName);
        }
        let messageId = this.message_name_id_request[reqName];
        if (!messageId) {
            console.error("================= pbkiller encode error! protoMessageId not found! reqName = " + reqName);
        }

        let protoBuffer = new proto(reqData).toArrayBuffer();

        let pbBufferLen = protoBuffer.byteLength;//protobuf数据长度
        let bufferLen = 12 + pbBufferLen;

        let postBuffer = new ArrayBuffer(bufferLen);
        let dataView = new DataView(postBuffer);
        //1 pbBuffer长度（四个字节）
        dataView.setInt32(0, pbBufferLen);
        //2 session（四个字节）
        dataView.setInt32(4, session);
        //3 messageID（四个字节）
        dataView.setInt32(8, messageId);
        //4 protobuf数据
        let postArray = new Uint8Array(postBuffer, 12);
        let protoArray = new Uint8Array(protoBuffer);
        postArray.set(protoArray);

        return postBuffer;
    }

    test() {
        let reqName = "ExploreValueUpdatePush_5200011a";
        let reqData = {
            supply: 10
        }
        let reqProto = this.pb.Protos[reqName];
        let protoBuffer = new reqProto(reqData).toArrayBuffer();
        console.log("encode reqData =", reqData);

        let retProto = this.pb.Protos[reqName];
        let retData = retProto.decode(protoBuffer);
        console.log("decode retData =", retData);
    }
};